package com.frinder.base;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.commons.beanutils.BeanMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Condition;
import tk.mybatis.mapper.entity.Example;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * AbstractService
 */
@Transactional(rollbackFor = {Exception.class, ServiceException.class})
public abstract class AbstractService<T> implements Service<T> {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    protected Mapper<T> mapper;

    private Class<T> modelClass;    // 当前泛型真实类型的Class

    public AbstractService() {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        modelClass = (Class<T>) pt.getActualTypeArguments()[0];
    }

    public int save(T model) {
        try {
            Method createTime = modelClass.getMethod("setCreateTime", Date.class);
            createTime.setAccessible(true);
            createTime.invoke(model, new Date());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        int result = mapper.insertSelective(model);
        if (result <= 0) throw new ServiceException(ServiceException.Code.SAVE_FAIL);
        return result;
    }

    public int save(List<T> models) {
        try {
            Method createTime = modelClass.getMethod("setCreateTime", Date.class);
            createTime.setAccessible(true);
            Method updateTime = modelClass.getMethod("setUpdateTime", Date.class);
            updateTime.setAccessible(true);
            Method isDelete = modelClass.getMethod("setIsDeleted", Boolean.class);
            isDelete.setAccessible(true);
            models.stream().forEach(m -> {
                try {
                    createTime.invoke(m, new Date());
                    updateTime.invoke(m, new Date());
                    isDelete.invoke(m, false);
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            });
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        int result = mapper.insertList(models);
        if (result <= 0) throw new ServiceException(ServiceException.Code.SAVE_FAIL);
        return result;
    }

    public int deleteById(Long id) {
        int result = mapper.deleteByPrimaryKey(id);
        if (result <= 0) throw new ServiceException(ServiceException.Code.DELETE_FAIL);
        return result;
    }

    public int deleteByIds(String ids) {
        int result = mapper.deleteByIds(ids);
        if (result <= 0) throw new ServiceException(ServiceException.Code.DELETE_FAIL);
        return result;
    }

    public int update(T model) {
        int result = mapper.updateByPrimaryKeySelective(model);
        if (result <= 0) throw new ServiceException(ServiceException.Code.UPDATE_FAIL);
        return result;
    }

    public T findById(Long id) {
        return mapper.selectByPrimaryKey(id);
    }

    @Override
    public T findBy(String property, Object value) {
        try {
            T model = modelClass.newInstance();
            Field field = modelClass.getDeclaredField(property);
            field.setAccessible(true);
            field.set(model, value);
            return mapper.selectOne(model);
        } catch (Exception e) {
            throw new ServiceException(ServiceException.Code.MORE_THAN_ONE);
        }
    }

    @Override
    public List<T> findByIds(String ids) {
        return mapper.selectByIds(ids);
    }

    @Override
    public List<T> findByCondition(Condition condition) {
        return mapper.selectByCondition(condition);
    }

    @Override
    public List<T> findAll() {
        return mapper.selectAll();
    }

    @Override
    public PageInfo page(PageWrapper<T> page) {
        PageHelper.startPage(page.getPage(), page.getSize());
        Condition condition = createCondition(page);
        List<T> list = mapper.selectByCondition(condition);
        PageInfo pageInfo = new PageInfo(list);
        return pageInfo;
    }

    /**
     * @param page
     * @return
     */
    private Condition createCondition(PageWrapper<T> page) {
        Condition condition = new Condition(modelClass);
        try {
            Example.Criteria criteria = condition.createCriteria();
            if (null != page.getCreateTimeFrom()) {
                criteria.andGreaterThanOrEqualTo("createTime", page.getCreateTimeFrom());
            }
            if (null != page.getCreateTimeTo()) {
                criteria.andLessThanOrEqualTo("createTime", page.getCreateTimeTo());
            }
            Object target = page.getTarget();
            if (null != target) {
                Map<Object, Object> fields = new BeanMap(target);
                fields.forEach((k, v) -> {
                    String key = String.valueOf(k);
                    if (!"class".equalsIgnoreCase(key) && null != v) {
                        if (v instanceof String) {
                            criteria.andLike(key, String.format("%s%%", v));
                        } else if (v instanceof Date) {
                        } else {
                            criteria.andEqualTo(key, v);
                        }
                    }
                });
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return condition;
    }
}
